package com.young.filesystem.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.young.common.constants.Constants;
import com.young.common.execption.ServiceException;
import com.young.filesystem.model.FileClaims;
import com.young.filesystem.model.FileRecord;
import com.young.filesystem.mapper.FileRecordMapper;
import com.young.filesystem.service.FileUploadService;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

@Slf4j
@Service
public class FileUploadServiceImpl implements FileUploadService {

    @Value("${file.dir}")
    private String uploadFileDir;
    @Value("${file.allowType}")
    private String allowFileType;
    @Value("${file.maxTimes}")
    private Integer allowMaxTimes;

    private final static String IMG_TYPE = "JPGPNGJPEGGIF";

    private final HttpServletRequest request;

    private final FileRecordMapper fileRecordMapper;


    public FileUploadServiceImpl(@Autowired HttpServletRequest request, @Autowired FileRecordMapper fileRecordMapper) {
        checkTmpDir();
        this.request = request;
        this.fileRecordMapper = fileRecordMapper;
    }

    public static Map<String, Integer> callTimesMap = new HashMap<>();

    @Override
    public List<String> uploadFile(String prefix, List<MultipartFile> fileList, long expireTime, boolean flag, boolean withMark) {
        List<String> pathList = new ArrayList<>(fileList.size());
        if (checkAllowIP() < 0) {
            throw new ServiceException("今日已经超过调用次数!");
        }
        String filePrefix = !StringUtils.hasLength(prefix) && !"undefined".equals(prefix) ? prefix : Constants.createFormat("yyyyMMdd").format(new Date());
        String fileDir = uploadFileDir + File.separator + filePrefix;
        File dir = new File(fileDir);
        dir.setWritable(true, false);
        if (!dir.exists()) dir.mkdirs();
        for (MultipartFile file : fileList) {
            String originName = file.getOriginalFilename();
            log.info("上传源文件名称：[{}]", originName);
            if (null == originName || checkAllowFile(originName) < 0) {
                throw new ServiceException("不支持文件类型!");
            }
            String suffix = originName.substring(originName.lastIndexOf("."));
            Calendar cal = Calendar.getInstance();
            if (expireTime > 0) {
                cal.add(Calendar.MINUTE, (int) expireTime);
            } else {
                cal.add(Calendar.YEAR, 2);
            }
            long expiredTimeMillis = cal.getTime().getTime();
            FileClaims fileClaims = FileClaims.builder().o("young").e("B64").x(expiredTimeMillis).p(filePrefix).build().selfSignature();
            String newFileName = filePrefix + File.separator + fileClaims.toString() + suffix;
            BufferedOutputStream bos = null;
            FileOutputStream fos = null;
            RandomAccessFile randomAccessFileWriter = null;
            RandomAccessFile randomAccessFileReader = null;
            log.info("开始保存文件...");
            try {
                long startMs = System.currentTimeMillis();
                // 判断文件大小 是否超过 100M ?
                List<File> shareFiles = new ArrayList<>();
                if (file.getSize() > 1024 * 1024 * 1024) {

                    // TODO !!! 经过测试，好像效率还没有transferTo高 :)

                    log.info("[{}]文件大小: {}", file.getOriginalFilename(), file.getSize());
                    InputStream fis = file.getInputStream();

                    // 分片大小 5M ?
                    int shareSize = 5 * 1024 * 1024;
                    // 分片
                    int shareNum = (int) Math.ceil(file.getSize() * 1.0 / shareSize);
                    int remainSize = (int) file.getSize() % shareSize;
                    log.info("分片数: {}", shareNum);

                    for (int s = 0; s < shareNum; s++) {
                        byte[] tmpBytes = new byte[s == shareNum - 1 ? remainSize : shareSize];
                        File temp = new File(uploadFileDir + File.separator + System.currentTimeMillis() + "tmp" + s);
                        IOUtils.read(fis, tmpBytes, 0, s == shareNum - 1 ? remainSize : shareSize);
                        fos = new FileOutputStream(temp);
                        bos = new BufferedOutputStream(fos);
                        bos.write(tmpBytes);
                        bos.flush();
                        shareFiles.add(temp);
                    }

                }

                File newFile = new File(uploadFileDir + File.separator + newFileName);

                if (shareFiles.isEmpty()) {
                    if (isImg(originName) >= 0 && withMark) {
                        // TODO 图片添加水印 时间戳
                        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                        boolean success = waterMarkText(simpleDateFormat.format(new Date()), file.getInputStream(), newFile, suffix.substring(1));
                        if (!success){
                            file.transferTo(newFile);
                        }
                    } else {
                        file.transferTo(newFile);
                    }
                } else {
                    randomAccessFileWriter = new RandomAccessFile(newFile, "rw");
                    byte[] bytes = new byte[1024];
                    for (int i = 0; i < shareFiles.size() ;i++) {
                        File tmp = shareFiles.get(i);
                        randomAccessFileReader = new RandomAccessFile(tmp, "r");
                        int len;
                        while ((len = randomAccessFileReader.read(bytes)) != -1) {
                            randomAccessFileWriter.write(bytes, 0, len);
                        }
                        tmp.delete();
                    }
                }

                log.info("保存文件[{}]成功！耗时[{}]", newFileName, System.currentTimeMillis() - startMs);
                pathList.add(flag ? Constants.SERVER_ADDRESS + File.separator + "file" + File.separator + newFileName : newFileName);
                FileRecord fileRecord = FileRecord.builder().fileName(newFileName).status(1).expireTime(expiredTimeMillis).createTime(new Date()).salt(fileClaims.getS()).build();
                try {
                    fileRecord.setUserId(Long.parseLong(prefix));
                } catch (Exception ignored) {}
                fileRecordMapper.insert(fileRecord);

                // TODO Attention!!! 生成压缩图 需要判断超过多少?M
                log.info("文件大小: {}", newFile.length());
                if (isImg(originName) > 0 && newFile.length() >= 1 * 1024 * 1024 && newFile.length() < 2 * 1024 * 1024) {
                    Thumbnails.of(newFile).scale(0.8).outputQuality(.8).toFile(newFile);
                }
                if (isImg(originName) > 0 && newFile.length() > 2 * 1024 * 1024) {
                    Thumbnails.of(newFile).scale(0.6).outputQuality(.8).toFile(newFile);
                }
            }  catch (Exception e) {
                throw new ServiceException(e, "传输异常!");
            } finally {
                try {
                    if (null != fos) fos.close();
                    if (null != bos) bos.close();
                    if (null != randomAccessFileReader) randomAccessFileReader.close();
                    if (null != randomAccessFileWriter) randomAccessFileWriter.close();

                } catch (Exception ignored) {}
            }
        }
        return pathList;
    }

    @Override
    public void deleteFile() {
        try {
            QueryWrapper<FileRecord> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("status", 1);
            List<FileRecord> fileList = fileRecordMapper.selectList(queryWrapper);
            log.info("数据库文件记录数[{}]", fileList.size());
            fileList.forEach(f -> {
                // 超过 2 年时间的文件删除
                if (System.currentTimeMillis() > f.getExpireTime()) {
                    String fullName = uploadFileDir + File.separator + f.getFileName();
                    File realFile = new File(fullName);
                    if (realFile.exists()) {
                        int code =  realFile.delete() ? 1 : 0;
                        log.info("删除文件[{}]{}！", f.getFileName(), code == 1 ? "成功" : "失败");
                        try {
                            File tempFile = new File(String.join("tmp.", fullName.split("\\.")));
                            tempFile.delete();
                        } catch (Exception ignored) {}
                    }
                    f.setStatus(0);
                    fileRecordMapper.updateById(f);
                }
            });
        } catch (Exception ignored) { }
    }

    private void checkTmpDir() {
        try {
            log.info("创建文件临时目录~");
            // 初始化目录
            File file = new File("/data/upload_tmp");
            if (!file.exists()) {
                file.mkdir();
            }
        } catch (Exception ignored) { }
    }

    private int checkAllowFile(String name) {
        if (null == name || !name.contains(".")) return -1;
        String suffix = name.substring(name.lastIndexOf(".") + 1);
        return allowFileType.indexOf(suffix.toUpperCase());
    }

    private int isImg(String name) {
        if (null == name || !name.contains(".")) return -1;
        String suffix = name.substring(name.lastIndexOf(".") + 1);
        return IMG_TYPE.indexOf(suffix.toUpperCase());
    }

    private int checkAllowIP() {
        String ip = request.getHeader("x-forwarded-for");
        if (null == ip || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (null == ip || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (null == ip || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (null == ip || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (null == ip || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        log.info("File upload access address IP: [{}]", ip);
        if (callTimesMap.containsKey(ip)) {
            int times = callTimesMap.get(ip);
            if (times + 1 > allowMaxTimes) {
                return -1;
            } else {
                callTimesMap.put(ip, times + 1);
            }
        } else {
            callTimesMap.put(ip, 1);
        }
        return 1;
    }

    /**
     * 图片加文字水印
     * @param        text 水印内容
     * @param inputStream 图片数据
     * @param     newFile 文件存放地址
     * @param      suffix 文件类型后缀
     */
    private boolean waterMarkText(String text, InputStream inputStream, File newFile, String suffix) {
        try {
            BufferedImage target = ImageIO.read(inputStream);
            if (null == target) { return false; }
            BufferedImage bufferedImage = new BufferedImage(target.getWidth(), target.getHeight(), BufferedImage.TYPE_INT_RGB);
            Graphics2D graphics = bufferedImage.createGraphics();
            graphics.drawImage(target, 0, 0, target.getWidth(), target.getHeight(), null);
            graphics.setColor(Color.gray);
            int fontSize = (int) (target.getWidth() / 19 * 0.9);
            graphics.setFont(new Font("微软雅黑", Font.PLAIN, fontSize));
            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            graphics.drawString(text, 0, target.getHeight() - 10);
            graphics.dispose();
            if (!newFile.exists()) { newFile.createNewFile(); }
            ImageIO.write(bufferedImage, suffix, newFile);
            bufferedImage.flush();
            target.flush();
            return true;
        } catch (Exception e) {
            log.error("图片添加水印异常!", e);
        } finally {
            if (null != inputStream) {
                try { inputStream.close(); } catch (IOException ignored) { }
            }
        }
        return false;
    }

}
